﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;

namespace WeddingEventManagement_API
{
    public class ClientManager : IClientManager
    {

        #region Validation

        /// <summary>
        /// Checks if it is OK to delete a client
        /// </summary>
        /// <param name="client"></param>
        private void ValidateDelete(Client client)
        {
            foreach (Event evt in client.Events)
            {
                if (evt.StartDate > DateTime.UtcNow)
                {
                    throw new ClientPendingEventExcption(String.Format("The client you are trying to delete has an upcoming event on {0}",
                        evt.StartDate.ToShortDateString()),
                        evt);
                }
            }
        }

        /// <summary>
        /// Validates the phone numbers server side
        /// </summary>
        /// <param name="phoneNumbers"></param>
        private void ValidatePhoneNumbers(List<ClientPhone> phoneNumbers)
        {
            if (phoneNumbers == null || phoneNumbers.Count < 1)
            {
                throw new PhoneNumberNotProvidedException("You must provide at least one phone number for a client");
            }

            foreach (ClientPhone pnumber in phoneNumbers)
            {
                if (pnumber.AreaCode > 999 || pnumber.AreaCode < 100)
                {
                    throw new PhoneNumberFormatInvalidException(String.Format("Invalid phone number: {0}. The area code must be 3 digits in length.",
                        pnumber.AreaCode + "-" + pnumber.Exchange + "-" + pnumber.LineNumber),
                        pnumber.AreaCode, pnumber.Exchange, pnumber.LineNumber, pnumber.Extension);
                }
                if (pnumber.Exchange > 999 || pnumber.Exchange < 100)
                {
                    throw new PhoneNumberFormatInvalidException(String.Format("Invalid phone number: {0}. The Exchange must be 3 digits in length.",
                        pnumber.AreaCode + "-" + pnumber.Exchange + "-" + pnumber.LineNumber),
                        pnumber.AreaCode, pnumber.Exchange, pnumber.LineNumber, pnumber.Extension);
                }
                if (pnumber.LineNumber > 9999 || pnumber.LineNumber < 1000)
                {
                    throw new PhoneNumberFormatInvalidException(String.Format("Invalid phone number: {0}. The line number must be 4 digits in length.",
                        pnumber.AreaCode + "-" + pnumber.Exchange + "-" + pnumber.LineNumber),
                        pnumber.AreaCode, pnumber.Exchange, pnumber.LineNumber, pnumber.Extension);
                }
            }
        }

        /// <summary>
        /// Checks to see if the client exists already in the database before inserting
        /// </summary>
        /// <param name="firstName"></param>
        /// <param name="lastName"></param>
        /// <param name="email"></param>
        /// <param name="address"></param>
        private void CheckIfClientExists(string firstName, string lastName, string email, string address)
        {
            CreativeSystemsEntities db = new CreativeSystemsEntities();
            Client client = db.Clients.FirstOrDefault
            (
                i => i.FirstName.ToLower() == firstName.ToLower() &&
                i.LastName.ToLower() == lastName.ToLower() &&
                i.Email.ToLower() == email.ToLower()
            );

            if (client != null)
            {
                throw new ClientExistsException(String.Format("A Client already exists with the first name: {0}, "
                    + "Last Name: {1}, Email: {2}", firstName, lastName, email));
            }
        }

        #endregion

        #region Read

        public List<String> GetUniqueCities()
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                var cities = (from c in db.Clients where !String.IsNullOrEmpty(c.City) select c.City).OrderBy(m=>m).Distinct();
                return cities.ToList();
            }
        }

        /// <summary>
        /// Returns a list of all active clients that match a given filter for last and or first name
        /// </summary>
        /// <param name="filter"></param>
        /// <returns></returns>
        public List<Client> GetClients(string filter)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                //db.Configuration.LazyLoadingEnabled = false;
                var clients = db.Clients
                    .Include("ClientPhones")
                    .Include("ClientPhones.PhoneNumberType")
                    .Where(
                        i => i.IsActive == true
                            &&
                        (
                            i.FirstName.Contains(filter) || i.LastName.Contains(filter) ||
                            (i.FirstName + " " + i.LastName).Contains(filter) ||
                            (i.LastName + " " + i.FirstName).Contains(filter)
                        )
                    ).OrderBy(i => i.LastName);
                return clients.ToList();
            }
        }

        /// <summary>
        /// Returns all active clients
        /// </summary>
        /// <returns></returns>
        public List<Client> GetClients()
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                //db.Configuration.LazyLoadingEnabled = false;

                var clients = db.Clients
                    .Include("ClientPhones")
                    .Include("ClientPhones.PhoneNumberType")
                    .Where(i => i.IsActive == true);
                return clients.ToList();
            }
        }

        /// <summary>
        /// Returns all active clients
        /// </summary>
        /// <returns></returns>
        public List<Client> GetInactiveClients()
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                //db.Configuration.LazyLoadingEnabled = false;

                var clients = db.Clients
                    .Include("ClientPhones")
                    .Include("ClientPhones.PhoneNumberType")
                    .Where(i => i.IsActive == false);
                return clients.ToList();
            }
        }

        /// <summary>
        /// Returns a single client
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public Client GetClient(int id)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                //db.Configuration.LazyLoadingEnabled = false;

                Client client = (from c in db.Clients
                                    .Include("ClientPhones")
                                    .Include("ClientPhones.PhoneNumberType")
                                    .Include("Events")
                                    .Include("Events.EventType")
                                    .Include("Events.Invoices")
                                 where c.ClientID == id && c.IsActive == true
                                 select c).FirstOrDefault();
                return client;
            }
        }

        /// <summary>
        /// returns a list of the most recently viewed clients
        /// </summary>
        /// <param name="num">Number of recent clients to return</param>
        /// <returns></returns>
        public List<Client> GetMostRecentlyViewedClients(int num)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                return db.Clients
                    .Include("ClientPhones")
                    .Include("ClientPhones.PhoneNumberType")
                    .Where(i => i.IsActive == true && i.LastViewed != null)
                    .OrderByDescending(i => i.LastViewed.Value)
                    .Take(num)
                    .ToList();
            }
        }

        /// <summary>
        /// returns a list of the most recently added clients
        /// </summary>
        /// <param name="num">number of recent clients to return</param>
        /// <returns></returns>
        public List<Client> GetMostRecentlyAddedClients(int num)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                var prev = DateTime.UtcNow.AddDays(-5);
                return db.Clients
                    .Include("ClientPhones")
                    .Include("ClientPhones.PhoneNumberType")
                    //.Where(i => i.DateCreated > prev && i.IsActive == true).OrderByDescending(i => i.DateCreated).Take(num).ToList();
                    .Where(i => i.IsActive == true).OrderByDescending(i => i.DateCreated).Take(num).ToList();
            }
        }

        public List<ClientIsotope> GetIsotopeClients()
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                var today = DateTime.UtcNow;
                var clients = (from c in db.Clients
                               where c.IsActive == true
                               select new ClientIsotope()
                               {
                                   ID = c.ClientID,
                                   Email = c.Email,
                                   FirstName = c.FirstName,
                                   LastName = c.LastName,
                                   Phone = c.ClientPhones.FirstOrDefault(),
                                   City = c.City,
                                   Created = c.DateCreated,
                                   Viewed = c.LastViewed.HasValue ? c.LastViewed.Value : DateTime.MinValue,
                                   Updated = c.LastUpdated.HasValue ? c.LastUpdated.Value : DateTime.MinValue
                               });
                return clients.ToList();
            }
        }

        #endregion

        #region Create, Update, Delete

        /// <summary>
        /// Creates a client in the database
        /// </summary>
        /// <returns>The ClientID of the newly created client</returns>
        public int CreateClient(string firstName, string lastName, string email, string address, string city, Province provence, 
            string postalCode, string notes, List<ClientPhone> phoneNumbers, Guid creatorUserID)
        {
            //Validate info
            CheckIfClientExists(firstName, lastName, email, address);
            ValidatePhoneNumbers(phoneNumbers);

            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                var currentDateTime = DateTime.UtcNow;

                //Create client and save to DB
                Client client = new Client
                {
                    FirstName = firstName,
                    LastName = lastName,
                    Email = email,
                    Address = address,
                    PostalCode = postalCode,
                    Provence = provence.ToString(),
                    City = city,
                    Notes = notes,
                    CreatorUserID = creatorUserID,
                    DateCreated = currentDateTime,
                    IsActive = true,
                    LastUpdated = null
                };
                db.Clients.Add(client);
                db.SaveChanges();
                
                //Get the newly created client object so we can have the generated ClientID
                Client newClient = db.Clients.Where(i => i.FirstName == firstName &&
                    i.LastName == lastName &&
                    i.Email == email &&
                    i.DateCreated == currentDateTime &&
                    i.CreatorUserID == creatorUserID).FirstOrDefault();

                //Add phone numbers now that we have clientID and save to DB
                foreach (ClientPhone pNum in phoneNumbers)
                {
                    pNum.ClientID = newClient.ClientID;
                }
                newClient.ClientPhones = phoneNumbers;
                db.SaveChanges();

                return newClient.ClientID;
            }

        }

        /// <summary>
        /// Removes a client from the system by marking the entry as inactive in the database
        /// </summary>
        /// <param name="clientId"></param>
        public void DeleteClient(int clientId, Guid updateUserId)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                db.Configuration.LazyLoadingEnabled = false;
                Client client = db.Clients.Include("Events").FirstOrDefault(i => i.ClientID == clientId);

                //Ensure the client does not have an upcoming event
                ValidateDelete(client);

                //We set the client to Inactive in the database instead of deleting the entry
                client.UpdateUserId = updateUserId;
                client.LastUpdated = DateTime.UtcNow;
                client.IsActive = false;
                db.SaveChanges();
            }
        }

        /// <summary>
        /// Update a client's data
        /// </summary>
        /// <param name="client"></param>
        public void UpdateClient(Client client, Guid userID)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                // Validate
                ValidatePhoneNumbers(client.ClientPhones.ToList());

                // Update UpdateUserID field
                client.UpdateUserId = userID;

                // Attach Phones to Update
                foreach (var phone in client.ClientPhones)
                {
                    if (phone.PhoneID == 0)
                    {
                        db.ClientPhones.Add(new ClientPhone() { AreaCode = phone.AreaCode, Exchange = phone.Exchange, LineNumber = phone.LineNumber, Extension = phone.Extension, ClientID = client.ClientID, PhoneNumberTypeID = phone.PhoneNumberTypeID } );
                    }
                    else
                    {
                        db.ClientPhones.Attach(phone);
                        db.Entry(phone).State = System.Data.EntityState.Modified;
                    }
                }

                // Attach and Update Client Object
                db.Clients.Attach(client);
                db.Entry(client).State = System.Data.EntityState.Modified;

                db.SaveChanges();
            }
        }

        public bool DeletePhoneNumber(int id)
        {
            using (CreativeSystemsEntities db = new CreativeSystemsEntities())
            {
                var phone = db.ClientPhones.Where(p => p.PhoneID == id).Single();

                // check if we can delete it
                if (phone.Client.ClientPhones.Count - 1 <= 0)
                {
                    throw new PhoneNumberNotProvidedException("You must provide at least one phone number for a client");
                }
                else
                {
                    db.ClientPhones.Remove(phone);
                    return db.SaveChanges() > 0;
                }
                
            }
        }

        #endregion

    }
}
